#!/usr/bin/env python2

import os
import sys
import time
import pickle
import socket
import re
import string
import getopt
import uuid
import errno

from utils import Exp, _dmsg, _dwarn, _derror, _get_value, _ntoa, _aton, _exec_pipe, _exec_pipe1, _exec_shell, \
        _str2dict, _hosts_load, _traceback, _get_network, _exchange_maskint, _ldconfig_init

LICH_REP_MAX = 10

class Config:

    def __init__(self):
        #self.hostname = socket.gethostname()
        self.shm = '/dev/shm/lich4'

        home = os.path.abspath(os.path.split(os.path.realpath(__file__))[0] + "/../..")
        self.inspect = os.path.join(home, 'lich/libexec/lich.inspect')
        self.lichbd = os.path.join(home, 'lich/libexec/lichbd')
        self.admin = os.path.join(home, 'lich/libexec/lich.admin')
        self.lichfs = os.path.join(home, 'lich/libexec/lichfs')
        self.lichd = os.path.join(home, 'lich/sbin/lichd')
        self.lib = os.path.join(home, 'lich/lib')
        self.log_path = os.path.join(home, 'log')

        _ldconfig_init(home)

        try:
            (res, err) = _exec_pipe1([self.admin, '--configdump'], 0, False)
        except Exp, e:
            if e.out:
                _derror(str(e.out))
            if e.err:
                _derror(str(e.err))
            exit(e.errno)

        if 'ump' in os.environ.keys():
            key,ip,vswitch,hostname = self.unique_key(home)
            unique = '%s+%s'%(key,ip)
            if (not unique == '+') and (not os.environ['ump'] == unique) and (not os.environ['ump']==''):
                show_ip = ip
                if vswitch:
                    show_ip = "%s,%s"%(vswitch,hostname)
                if not show_ip :
                    show_ip = hostname

                _derror('key is not equal,ip:%s'%show_ip)
                exit(2)

        d = _str2dict(res[:-1])

        etc = home + "/etc"
        self.clusterconf = etc + "/cluster.conf"
        self.cacheconf = self._parse_cacheconf(etc + "/cache.conf")

        self._parse_configure(d)
        self.hosts_load()
        self.shell_port = 27901
        self.dump_port = 27902

        self.ADMIN_BH_WAIT = 60 * 10
        self.qosnum = 10

    def refresh(self):
        try:
            (res, err) = _exec_pipe1([self.admin, '--configdump'], 0, False)
        except Exp, e:
            if e.out:
                _derror(str(e.out))
            if e.err:
                _derror(str(e.err))
            exit(e.errno)
        d = _str2dict(res[:-1])
        self._parse_configure(d)

    def unique_key(self,home):
        keyfile = '%s/tmp/unique_key'%home
        if not os.path.isfile(keyfile):
            return '','',False,''

        output = open(keyfile, 'rb')
        try:
            data = pickle.load(output)
        except:
            return '','',False,''

        if data.has_key('vswitch') or data.has_key('hostname'):
            return data['unique_key'],data['ip'],data['vswitch'],data['hostname']
        else:
            return data['unique_key'],data['ip'],False,None

    def _parse_configure(self, d):
        #print (d)
        home = d['globals.home']
        self.hostname = d['globals.hostname']
        self.home = home
        self.rpc_timeout = int(d['globals.rpc_timeout'])
        self.lich = home + "/lich/"
        self.tmp = home + "/tmp/"
        self.replica_max = int(d['globals.replica_max'])
        self.wmem_max = int(d['globals.wmem_max'])
        self.rmem_max = int(d['globals.rmem_max'])
        self.clustername = d['globals.clustername']
        self.storage_area_max_node = int(d['globals.storage_area_max_node'])
        self.meta = int(d['metadata.meta'])
        self.disk_keep = int(d['cdsconf.disk_keep'])
        self.crontab = 1 if d['globals.crontab'] == 'on' else 0
        self.localize = 1 if d['globals.localize'] == 'on' else 0
        self.writeback = 1 if d['globals.writeback'] == 'on' else 0
        self.cache_enable = 1 if d['globals.cache_enable'] == 'on' else 0
        self.cache_type = d['globals.cache_type']
        self.spdk = 1 if d['globals.spdk'] == 'on' else 0
        self.cache_seq_cutoff = "256k"
        self.cache_wb_percent = "40"
        self.cache_mode = "writethrough"
        if "%" in d['globals.polling_core']:
            self.polling_core = 0
        else:
            self.polling_core = int(d['globals.polling_core'])
        self.memcache_seg = int(d['globals.memcache_seg'])
        self.memcache_count = int(d['globals.memcache_count'])
        result = self.memcache_count & (self.memcache_count - 1)
        if result:
            raise Exp(errno.EPERM, "memcache_count[%s] must be a power of 2,please update '%s/etc/lich.conf'"
                    % (self.memcache_count, self.home))

        if (d['globals.nohosts'] == 'on'):
            self.hosts_conf = home + '/etc/hosts.conf'
            self.nohosts = True
        else:
            self.hosts_conf = "/etc/hosts"
            self.nohosts = False

        if d['globals.testing'] == 'on':
            #print 'Warning: globals.testing is on'
            self.testing = 1
        else:
            self.testing = 0

        if d['globals.cgroup'] == 'on':
            self.cgroup = 1
        else:
            self.cgroup = 0

        if d['globals.cleanlogcore'] == 'on':
            self.cleanlogcore = 1
        else:
            self.cleanlogcore = 0
        self.coredump = 1 if d['globals.coredump'] == 'on' else 0

        self.iqn = d['iscsi.iqn']
        self.iscsi_port = int(d['iscsi.port'])

        self.iscsi_timeout = int(d['iscsi.timeout'])

        self.iscsi_vip = ""
        if ('iscsi.vip' in d):
            self.iscsi_vip = d['iscsi.vip']

        self.ucarp_vip = ""
        if ('ucarp.vip' in d):
            self.ucarp_vip = d['ucarp.vip']

        if (self.meta < 3):
            self.meta = 3

    def _parse_cacheconf(self, path):
        cacheconf = {'ssd':{'raid_cache':'disable', 'disk_cache':'disable', 'skip':False},\
                    'hdd':{'raid_cache':'enable', 'disk_cache':'disable', 'skip':False}}

        if os.path.exists(path):
            fd = open(path, 'r')
            for line in fd.readlines():
                if line.strip() == '' or line.strip().startswith('#'):
                    continue

                info = line.lower().split()
                dev = info[0]
                info.remove(info[0])

                if dev == 'ssd' or dev == 'hdd' or dev.startswith('disk') or dev == 'sysdev':
                    if dev not in cacheconf:
                        cacheconf[dev] = {}
                    if 'skip' in info:
                        cacheconf[dev]['skip'] = True
                        info.remove('skip')
                    for item in info:
                        if item.split(':')[0] == 'raid_cache':
                            if item.split(':')[1] == 'enable' or item.split(':')[1] == 'disable' or item.split(':')[1] == 'policy':
                                cacheconf[dev]['raid_cache'] = item.split(':')[1]
                        elif item.split(':')[0] == 'disk_cache':
                            if item.split(':')[1] == 'enable' or item.split(':')[1] == 'disable':
                                cacheconf[dev]['disk_cache'] = item.split(':')[1]
                        elif item.split(':')[0] == 'cache_policy':
                            cacheconf[dev]['cache_policy'] = item.split(':')[1]
            fd.close()

        return cacheconf

    def hosts_load(self, configfile=None):
        if configfile is None:
            configfile = self.clusterconf
        self.hosts_version, self.hosts = _hosts_load(configfile)

    def __str__(self):
        s = "home: %s\nhosts version: %u\ninstence: %s\n" % (self.lich, self.hosts_version, self.hosts)
        #for (k,v) in self.hosts.items():
        #    s += "(host %s instence %d)," % (k, v)
        return s

    def hosts_dump(self, configfile):
        s = "#version=%d\n" % (self.hosts_version)
        for (k, v) in self.hosts.items():
            if (v == 1):
                s += "%s\n" % (k)
            else:
                s += "%s:%d\n" % (k, v)
        fd = open(configfile, 'w+b')
        fd.write(s)
        fd.close()

    def list2dict(self, list1):
        d = {}
        for i in list1:
            k = i.split(':')
            if (len(k) == 1):
                d[k[0]] = 1
            else:
                d[k[0]] = int(k[1])
        return d

    def generateuuid(self):
        configfile = self.home + "/etc/lich.conf"
        if not os.path.exists(configfile):
            raise Exp(errno.ENOENT, configfile + " not found")

        fileobject = open(configfile, "r")
        configcontent = fileobject.read()
        fileobject.close()

        newuuid = str(uuid.uuid1()).replace('-', '')
        newcontent = None
        res = re.findall(r'^\s*#?\s*uuid \w+;', configcontent, re.M)
        if res:
            newcontent = configcontent.replace(res[0], '\tuuid ' + newuuid + ';')
        else:
            contentlines = configcontent.splitlines()
            num = 0
            for line in contentlines:
                num += 1
                if line.startswith("globals"):
                    break
            contentlines.insert(num, '\tuuid ' + newuuid + ';')
            newcontent = '\n'.join(contentlines)

        fileobject = open(configfile, "w")
        fileobject.write(newcontent)
        fileobject.close()

    def getclusteruuid(self):
        configfile = self.home + "/etc/lich.conf"
        if not os.path.exists(configfile):
            raise Exp(errno.ENOENT, configfile + " not found")

        fileobject = open(configfile, "r")
        configcontent = fileobject.read()
        fileobject.close()

        res = re.findall(r'^\s*uuid (\w+);', configcontent, re.M)
        if res:
            return res[0]

        return None

    def createcluster(self, list1):
        if (len(self.hosts)):
            raise Exp(errno.EEXIST, "cluster already exist")

        d = self.list2dict(list1)
        for (k, v) in d.items():
            self.hosts[k] = v
        self.hosts_dump(self.clusterconf)

    def addnode(self, list1):
        if (len(self.hosts) == 0):
            raise Exp(errno.ENOENT, "cluster not already")

        d = self.list2dict(list1)
        for (k, v) in d.items():
            if (self.hosts.get(k)):
                raise Exp(errno.EEXIST, "host %s already exist" %(k))
            self.hosts[k] = v
        self.hosts_version = self.hosts_version + 1
        self.hosts_dump(self.clusterconf)

    def dropnode(self, list1):
        if (len(self.hosts) == 0):
            raise Exp(errno.ENOENT, "cluster not already")

        for i in list1:
            k = i.split(':')
            if (self.hosts.get(k[0]) == None):
                raise Exp(errno.EEXIST, "host %s not exist" %(k[0]))
            self.hosts.pop(k[0])
        self.hosts_version = self.hosts_version + 1
        self.hosts_dump(self.clusterconf)

    def __network(self, net):
        n = net.split('/')
        net = n[0]
        mask = int(n[1])
        mask1 = 0
        m = 32 - mask
        for i in range(m, 32):
            mask1 = mask1 + (1 << i)

        mask1 = socket.htonl(mask1)
        return (net, _ntoa(mask1))

    def network(self):
        configfile = self.home + "/etc/lich.conf"
        config = _get_value(configfile)
        n = re.search("networks\s*\{[^\\}]+}", config, re.M).group()
        lst = re.findall("\d+\.\d+\.\d+\.\d+\/\d+", n, re.M)
        net = []
        for i in lst:
            net.append(self.__network(i))
        return net

    def get_lich_network(self):
        lich_net = {}
        confignet = self.network()
        localnet = _get_network()

        ip = self.ucarp_vip.split('/')[0]
        mask = _exchange_maskint(int(self.ucarp_vip.split('/')[1]))
        net = _aton(ip) & _aton(mask)

        for (k,v) in localnet.items():
            n = _aton(v[0]) & _aton(v[1])
            if n == net:
                lich_net[k] = v

        return lich_net

    def is_ucarpconfd(self):
        return self.ucarp_vip.strip() != ""

    def is_vipconfd(self):
        configfile = self.home + "/etc/lich.conf"
        config = _get_value(configfile)
        n = re.search("networks\s*\{[^\\}]+}", config, re.M).group()
        lst = re.findall("\d+\.\d+\.\d+\.\d+\/\d+", n, re.M)

        if lst:
            m = re.search("iscsi_vip ", config, re.M)
            if m is not None:
                m = re.search("#\s*iscsi_vip ", config, re.M)
                if m is None:
                    return True
        return False

    def sysstat(self, force=False):
        if force:
            cmds = [self.lich + "/libexec/lich.admin",  "--sysstat", "--force"]
        else:
            cmds = [self.lich + "/libexec/lich.admin",  "--sysstat"]

        s = _exec_pipe(cmds, p=False)
        d = _str2dict(s)
        for (k, v) in d.items():
            p = v.split(',')
            d[k] = (int(p[0]), int(p[1]))

        return d

    def load_hosts(self):
        res = _exec_pipe([self.admin, '--hostsdump'], 0, False)[:-1]
        d = _str2dict(res)
        return d

    def ip2hostname_nohosts(self, ip, hosts=None):
        if hosts is None:
            hosts = self.load_hosts()

        if ip not in hosts:
            raise Exp(errno.ENOENT, "%s not found in %s with nohosts mode, please check %s" % (ip, hosts, self.hosts_conf))

        return hosts[ip]

    def hostname2ip_nohosts(self, hostname, hosts=None):
        if hosts is None:
            hosts = self.load_hosts()

        hosts_new = {}
        for ip in hosts.keys():
            hosts_new.update({hosts[ip]: ip})

        return hosts_new[hostname]

def usage():
    print ("usage:")
    print (sys.argv[0] + " --init")
    print (sys.argv[0] + " --createcluster <host1> <host2> <host3> ...")
    print (sys.argv[0] + " --addnode <host1> <host2> <host3> ...")
    print (sys.argv[0] + " --drop <host1> <host2> <host3> ...")
    print (sys.argv[0] + " --show")


def main():
    config = Config()
    try:
        opts, args = getopt.getopt(
                sys.argv[1:],
                'h', ['show', 'createcluster', 'addnode', 'dropnode', 'help']
                )
    except getopt.GetoptError, err:
        print str(err)
        usage()

    for o, a in opts:
        if o in ('--help'):
            usage()
            exit(0)
        elif o == '--show':
            print(config)
        elif o == '--createcluster':
            config.createcluster(sys.argv[2:])
        elif o == '--addnode':
            config.addnode(sys.argv[2:])
        elif o == '--dropnode':
            config.dropnode(sys.argv[2:])
        else:
            assert False, 'oops, unhandled option: %s, -h for help' % o
            exit(1)

if __name__ == '__main__':
    if (len(sys.argv) == 1):
        usage()
    else:
        main()
